package com.saobao.m.assistant.support;

import lombok.extern.slf4j.Slf4j;
import org.aopalliance.intercept.MethodInterceptor;
import org.aopalliance.intercept.MethodInvocation;
import org.apache.commons.collections.CollectionUtils;
import org.springframework.aop.framework.ProxyFactory;
import org.springframework.util.Assert;

import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.Collection;

/**
 * 代理无返回值的接口（如Listener或Callback），将调用扩散到所有指定的接口实例上。
 * @author wangyin.wy
 * @Date 2014-8-9
 */
@Slf4j
public final class Scatter {
	/**
	 * void返回类型名称
	 */
	public static final String VOID_TYPE_NAME = "void";
	/**
	 * 异常处理策略
	 * @author wangyin.wy
	 * @Date 2014-8-9
	 */
	public static interface ExceptioHandlePolicy {
		/**
		 * 处理异常
		 * @param e 异常实例
		 * @param sum 总共sum次调用
		 * @param index 第几次调用
		 */
		void handleException(Throwable e, int sum, int index);
	}
	/**
	 * 快速失败异常处理策略
	 */
	public static final ExceptioHandlePolicy FAST_FAIL_EXCEPTION_POLICY = new ExceptioHandlePolicy() {

		@Override
		public void handleException(Throwable e, int sum, int index) {
			throw new InvokeChildrenException(e);
		}
		
	};
	/**
	 * 忽略异常处理策略
	 */
	public static final ExceptioHandlePolicy IGNORE_EXCEPTION_POLICY = new ExceptioHandlePolicy() {

		@Override
		public void handleException(Throwable e, int sum, int index) {
			log.error("@INVOKE_CHILDREN_FAILED", e);
		}
		
	};
	/**
	 * 生成指定类型的代理实例，所有调用将扩散到指定的实例集上。
	 * @param children 实例集
	 * @param type 类型
	 * @param ehp 异常处理策略
	 * @return Scatter of type
	 */
	@SuppressWarnings("unchecked")
	public static <T> T create(final Collection<T> children, Class<T> type, ExceptioHandlePolicy ehp) {
		Assert.isTrue(CollectionUtils.isNotEmpty(children), "children can not be empty");
		Assert.notNull(type, "type can not be null");
		final ExceptioHandlePolicy toUseEhp = ehp == null ? FAST_FAIL_EXCEPTION_POLICY : ehp;
		//检查代理的方法是否试用
		for (Method method : type.getMethods()) {
			if (!method.getReturnType().getName().equals(VOID_TYPE_NAME)) {
				throw new UnsupportedOperationException("method must be void type");
			}
		}
		T r = null;
		ProxyFactory factory = new ProxyFactory(type, new MethodInterceptor() {
			
			@Override
			public Object invoke(MethodInvocation invocation) throws Throwable {
				Method method = invocation.getMethod();
				Object[] args = invocation.getArguments();
				int sum = children.size();
				int index = 1;
				for (T child : children) {
					try {
						method.invoke(child, args);
					} catch (Exception e) {
						//find out the root cause if possible
						if (e instanceof InvocationTargetException) {
							InvocationTargetException ite = (InvocationTargetException) e;
							toUseEhp.handleException(ite.getTargetException(), sum, index);
						} else {
							toUseEhp.handleException(e, sum, index);
						}
					}
					index++;
				}
				return null;
			}
		});
		r = (T) factory.getProxy();
		return r;
	}
	/**
	 * 调用接口实例失败异常
	 * @author wangyin.wy
	 * @Date 2014-8-9
	 */
	public static class InvokeChildrenException extends RuntimeException {
		/**
		 * serialVersionUID
		 */
		private static final long serialVersionUID = -3950702655484026192L;

		public InvokeChildrenException() {
			super();
		}

		public InvokeChildrenException(String message, Throwable cause) {
			super(message, cause);
		}

		public InvokeChildrenException(String message) {
			super(message);
		}

		public InvokeChildrenException(Throwable cause) {
			super(cause);
		}
		
	}
	
}